#include "pm_types.h"
#include "./pm.h"
#include "./pm_i.h"

static __u32 cpu_freq = 0;
static __u32 overhead = 0;
static __u32 backup_perf_counter_ctrl_reg = 0;
static __u32 backup_perf_counter_enable_reg = 0;

static __u32 match_event_counter(enum counter_type_e type);

void init_perfcounters (__u32 do_reset, __u32 enable_divider)
{
	// in general enable all counters (including cycle counter)
	__u32 value = 1;

	// peform reset:
	if (do_reset)
	{
		value |= 2;     // reset all counters to zero.
		value |= 4;     // reset cycle counter to zero.
	}

	if (enable_divider)
		value |= 8;     // enable "by 64" divider for CCNT.

	value |= 16;

	// program the performance-counter control-register:
	asm volatile ("mcr p15, 0, %0, c9, c12, 0" : : "r"(value));

	// enable all counters:
	value = 0x8000000f;
	asm volatile ("mcr p15, 0, %0, c9, c12, 1" : : "r"(value));

	// clear overflows:
	asm volatile ("MCR p15, 0, %0, c9, c12, 3" : : "r"(value));
	asm volatile ("dsb");
	asm volatile ("isb");

	return;
}

void backup_perfcounter(void)
{
	//backup performance-counter ctrl reg
	asm volatile ("MRC p15, 0, %0, c9, c12, 0\t\n": "=r"(backup_perf_counter_ctrl_reg)); 
	
	//backup enable reg
	asm volatile ("MRC p15, 0, %0, c9, c12, 1\t\n": "=r"(backup_perf_counter_enable_reg)); 

}

void restore_perfcounter(void)
{
	
	// restore performance-counter control-register:
	asm volatile ("mcr p15, 0, %0, c9, c12, 0" : : "r"(backup_perf_counter_ctrl_reg));

	// restore enable reg
	asm volatile ("mcr p15, 0, %0, c9, c12, 1" : : "r"(backup_perf_counter_enable_reg));
	
}
	
__u32 get_cyclecount (void)
{
	__u32 value;
	// Read CCNT Register
	asm volatile ("MRC p15, 0, %0, c9, c13, 0\t\n": "=r"(value));  
	return value;
}

void reset_counter(void)
{
	__u32 value = 0;

	asm volatile ("mrc p15, 0, %0, c9, c12, 0" : "=r"(value));
	value |= 4;     // reset cycle counter to zero.
	// program the performance-counter control-register:
	//__asm {MCR p15, 0, value, c9, c12, 0}
	asm volatile ("mcr p15, 0, %0, c9, c12, 0" : : "r"(value));
}

void change_runtime_env(void)
{
	__u32 start = 0;
	__u32 end = 0;

	//init counters:
	//init_perfcounters (1, 0);
	// measure the counting overhead:
	start = get_cyclecount();
	end = get_cyclecount();
	overhead = (end >= start) ? (end-start) : (0xffffffff - start + end);
	//busy_waiting();
	cpu_freq = mem_clk_get_cpu_freq();	
}

/*
 * input para range: 1-1000 us, so the max us_cnt equal = 1008*1000;
 */	
void delay_us(__u32 us)
{
	__u32 us_cnt = 0;
	__u32 cur = 0;
	__u32 target = 0;
	__u32 counter_enable = 0;
	//__u32 cnt = 0;


	if(cpu_freq > 1000){
		us_cnt = ((raw_lib_udiv(cpu_freq, 1000)) + 1)*us;
	}else{
		//32 <--> 32k, 1cycle = 1s/32k =32us 
	    goto out;
	}
	
	cur = get_cyclecount();
	target = cur - overhead + us_cnt;

	//judge whether counter is enable
	asm volatile ("mrc p15, 0, %0, c9, c12, 1" :"=r"(counter_enable));
	if( 0x8000000f != (counter_enable&0x8000000f) ){
	    init_perfcounters (1, 0);
	}
#if 1
	while(!counter_after_eq(cur, target)){
		cur = get_cyclecount();
		//cnt++;
	}
#endif
	

#if 0
	__s32 s_cur = 0;
	__s32 s_target = 0;
	__s32 result = 0;

	s_cur = (__s32)(cur);
	s_target = (__s32)(target);
	result = s_cur - s_target;
	if(s_cur - s_target >= 0){
		cnt++;
	}
	while((typecheck(__u32, cur) && \
			typecheck(__u32, target) && \
			((__s32)(cur) - (__s32)(target) >= 0))){
		
			s_cur = (__s32)(cur);
			s_target = (__s32)(target);
			if(s_cur - s_target >= 0){
				cnt++;				
			}
			cur = get_cyclecount();
	}
#endif
	//busy_waiting();

out:
	
	asm("dmb");
	asm("isb");
	return;
}


//the overflow limit is: 0x7fff,ffff / 720M = 2982ms;
//each delay cycle can not exceed 10ms, in case of overflow.
void delay_ms(__u32 ms)
{
    while(ms > 10){
	delay_us(10*1000);
	ms -= 10;
    }
	
    delay_us(ms*1000);
    return;
}

/*============================================== event counter ==========================*/
static __u32 match_event_counter(enum counter_type_e type)
{
	int cnter = 0;
	
	switch(type){
		case I_CACHE_MISS: 
			cnter = 0;
			break;
		case I_TLB_MISS: 
			cnter = 1;
			break;
		case D_CACHE_MISS: 
			cnter = 2;
			break;
		case D_TLB_MISS: 
			cnter = 3;
			break;

		default:
			break;
	
	}
	return cnter;

}

void init_event_counter (__u32 do_reset, __u32 enable_divider)
{
	// in general enable all counters (including cycle counter)
	__u32 value = 1;

	// peform reset:
	if (do_reset)
	{
		value |= 2;     // reset all counters to zero.
		value |= 4;     // reset cycle counter to zero.
	}

	if (enable_divider)
		value |= 8;     // enable "by 64" divider for CCNT.

	value |= 16;

	// program the performance-counter control-register:
	asm volatile ("mcr p15, 0, %0, c9, c12, 0" : : "r"(value));

	// enable all counters:
	value = 0x8000000f;
	asm volatile ("mcr p15, 0, %0, c9, c12, 1" : : "r"(value));

	// clear overflows:
	asm volatile ("MCR p15, 0, %0, c9, c12, 3" : : "r"(value));

	return;
}

void set_event_counter(enum counter_type_e type)
{
	
	__u32 cnter = 0;
	cnter = match_event_counter(type);

	//set counter selection reg
	asm volatile ("MCR p15, 0, %0, c9, c12, 5" : : "r"(cnter));

	//set event type
	asm volatile ("MCR p15, 0, %0, c9, c13, 1" : : "r"(type));

	asm volatile ("dsb");
	asm volatile ("isb");

	return;
}


int get_event_counter(enum counter_type_e type)
{
	int cnter = 0;
	int event_cnt = 0;
	cnter = match_event_counter(type);

	//set counter selection reg
	asm volatile ("MCR p15, 0, %0, c9, c12, 5" : : "r"(cnter));

	//read event counter
	asm volatile ("MRC p15, 0, %0, c9, c13, 2\t\n": "=r"(event_cnt)); 
	
	asm volatile ("dsb");
	asm volatile ("isb");

	return event_cnt;
}


